package raftnode

import (
	"context"
	"fmt"
	"net"
	"os"
	"path/filepath"
	"time"

	"frigate/internal/proto"
	"frigate/internal/store"
	"frigate/utils"

	"github.com/hashicorp/raft"
	"google.golang.org/grpc"
)

// Node 表示一个 Raft 节点
type Node struct {
	Raft      *raft.Raft   // 公开字段，供RPC服务访问
	Config    *raft.Config // 公开字段，供RPC服务访问
	addr      string
	dataDir   string
	store     *store.BoltStore
	joinAddr  string
	bootstrap bool
	rpcAddr   string       // RPC服务地址
	tcpAddr   *net.TCPAddr // 保存TCP地址引用
}

// Config 包含创建 Raft 节点所需的配置
type Config struct {
	NodeID    string
	Addr      string
	RPCAddr   string // RPC地址配置
	DataDir   string
	JoinAddr  string
	Bootstrap bool
}

// NewNode 创建一个新的 Raft 节点
func NewNode(config Config) (*Node, error) {
	// 创建 Raft 配置
	raftConfig := raft.DefaultConfig()
	raftConfig.LocalID = raft.ServerID(config.NodeID)
	// 设置选举超时时间在 3-5 秒范围内
	raftConfig.ElectionTimeout = 3 * time.Second
	raftConfig.HeartbeatTimeout = time.Second
	raftConfig.CommitTimeout = 50 * time.Millisecond

	// 解析TCP地址
	tcpAddr, err := net.ResolveTCPAddr("tcp", config.Addr)
	if err != nil {
		return nil, fmt.Errorf("failed to resolve TCP address: %w", err)
	}

	node := &Node{
		Config:    raftConfig,
		addr:      config.Addr,
		rpcAddr:   config.RPCAddr,
		tcpAddr:   tcpAddr,
		dataDir:   config.DataDir,
		joinAddr:  config.JoinAddr,
		bootstrap: config.Bootstrap,
		store:     store.NewBoltStore(),
	}

	return node, nil
}

// Start 启动 Raft 节点
func (n *Node) Start() error {
	// 创建数据目录（如果不存在）
	if err := ensureDirExists(n.dataDir); err != nil {
		return fmt.Errorf("failed to create data directory: %w", err)
	}

	// 检查是否已有 Raft 数据，决定是否需要 bootstrap
	hasExistingData := checkExistingRaftData(n.dataDir)

	// 设置日志存储 - 使用单独的文件名
	logStore, err := store.NewBoltStoreWithPathAndName(n.dataDir, "raft_log.db")
	if err != nil {
		return fmt.Errorf("failed to create log store: %w", err)
	}

	// 设置稳定存储 - 使用单独的文件名
	stableStore, err := store.NewBoltStoreWithPathAndName(n.dataDir, "raft_stable.db")
	if err != nil {
		return fmt.Errorf("failed to create stable store: %w", err)
	}

	// 设置快照存储
	snapshotStore, err := raft.NewFileSnapshotStore(n.dataDir, 3, nil)
	if err != nil {
		return fmt.Errorf("failed to create snapshot store: %w", err)
	}

	// 设置传输层
	transport, err := raft.NewTCPTransport(n.addr, n.tcpAddr, 3, 10*time.Second, nil)
	if err != nil {
		return fmt.Errorf("failed to create TCP transport: %w", err)
	}

	// 创建 FSM（使用新的构造函数）
	fsm := store.NewFSM(n.store)

	// 创建 Raft 实例
	r, err := store.NewRaft(n.Config, fsm, logStore, stableStore, snapshotStore, transport)
	if err != nil {
		return fmt.Errorf("failed to create raft: %w", err)
	}
	// 保存transport引用，供将来使用
	n.Raft = r

	// 配置存储的 Raft 实例（使用新的 SetRaft 方法）
	n.store.SetRaft(r)

	// 只有在没有现有数据且明确指定了bootstrap时才进行bootstrap
	if n.bootstrap && !hasExistingData {
		servers := raft.Configuration{
			Servers: []raft.Server{
				{
					ID:      n.Config.LocalID,
					Address: transport.LocalAddr(),
				},
			},
		}

		future := r.BootstrapCluster(servers)
		if err := future.Error(); err != nil {
			return fmt.Errorf("failed to bootstrap cluster: %w", err)
		}
		utils.Logger.Info().Str("node", string(n.Config.LocalID)).Msg("Successfully bootstrapped new cluster")
	} else if n.bootstrap && hasExistingData {
		// 如果有现有数据但仍指定了bootstrap，记录警告并以follower身份启动
		utils.Logger.Warn().Str("node", string(n.Config.LocalID)).
			Msg("Skipping bootstrap as existing Raft data found, starting as follower")
	}

	// 如果指定了 join 地址，加入集群
	// 注意：即使有现有数据，如果指定了join，也尝试加入（这可能是用户明确的意图）
	if n.joinAddr != "" {
		if err := n.joinCluster(n.joinAddr); err != nil {
			// 如果加入失败，但已有数据，继续以独立节点运行
			if hasExistingData {
				utils.Logger.Warn().Err(err).Str("node", string(n.Config.LocalID)).
					Msg("Failed to join cluster but will continue with existing data")
			} else {
				return fmt.Errorf("failed to join cluster: %w", err)
			}
		}
	}

	utils.Logger.Info().Str("node", string(n.Config.LocalID)).Str("addr", n.addr).Msg("Raft node started")

	return nil
}

// checkExistingRaftData 检查是否存在已有的 Raft 数据文件
func checkExistingRaftData(dataDir string) bool {
	// 检查是否存在 Raft 日志或稳定存储文件
	logFilePath := filepath.Join(dataDir, "raft_log.db")
	stableFilePath := filepath.Join(dataDir, "raft_stable.db")
	snapshotDir := filepath.Join(dataDir, "snapshots")

	// 如果任何一个文件或目录存在，说明节点之前已有数据
	if _, err := os.Stat(logFilePath); err == nil {
		return true
	}
	if _, err := os.Stat(stableFilePath); err == nil {
		return true
	}
	if _, err := os.Stat(snapshotDir); err == nil {
		return true
	}

	return false
}

// joinCluster 加入已有的 Raft 集群（使用RPC调用）
func (n *Node) joinCluster(joinAddr string) error {
	// 构建要添加的服务器配置
	serverID := n.Config.LocalID
	serverAddr := raft.ServerAddress(n.addr)

	// 使用gRPC连接到现有集群节点
	conn, err := grpc.Dial(joinAddr, grpc.WithInsecure(), grpc.WithBlock(), grpc.WithTimeout(5*time.Second))
	if err != nil {
		return fmt.Errorf("failed to connect to cluster node %s: %w", joinAddr, err)
	}
	defer conn.Close()

	// 创建客户端
	client := proto.NewClusterServiceClient(conn)

	// 发送加入请求
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	resp, err := client.Join(ctx, &proto.JoinRequest{
		NodeId:   string(serverID),
		RaftAddr: string(serverAddr),
	})

	if err != nil {
		return fmt.Errorf("join request failed: %w", err)
	}

	if !resp.Success {
		return fmt.Errorf("join rejected: %s", resp.Message)
	}

	utils.Logger.Info().Str("node", string(serverID)).Str("join_addr", joinAddr).Msgf("Successfully joined cluster: %s", resp.Message)

	return nil
}

// GetRole 返回当前节点的角色（Leader/Follower/Candidate）
func (n *Node) GetRole() string {
	if n.Raft == nil {
		return "Unknown"
	}
	return n.Raft.State().String()
}

// Shutdown 关闭 Raft 节点
func (n *Node) Shutdown() error {
	if n.Raft != nil {
		future := n.Raft.Shutdown()
		if err := future.Error(); err != nil {
			return err
		}
	}
	return nil
}

// GetStore 返回节点的存储接口
func (n *Node) GetStore() *store.BoltStore {
	return n.store
}

// GetRPCAddr 返回RPC服务地址
func (n *Node) GetRPCAddr() string {
	return n.rpcAddr
}

// ensureDirExists 确保目录存在
func ensureDirExists(dir string) error {
	dataDir := filepath.Dir(dir)
	if dataDir == "." {
		dataDir = dir
	}
	if _, err := os.Stat(dataDir); os.IsNotExist(err) {
		return os.MkdirAll(dataDir, 0755)
	}
	return nil
}
